//sws_client.cpp
//patrick braga-henebry
//mark pasquier
//netowrks
// due 28 feb 08

//std stuff
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

//netowrk
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>

#include <time.h>
#include <sys/time.h>
#include <errno.h>


#include <iostream>
#include <vector>
using namespace std;

int portnum;
int resubmissions = 0;
int ack_count = 0;

class msg_time
{
	public:
		time_t stime;
		int msg_number;
		bool set;
		int resubs;
		msg_time();
};

msg_time::msg_time()
{
	stime = time(NULL);
	msg_number = -1;
	set = false; // Set to true when used
	resubs = 0; // number of resubmissions
}

////////////////
// Check for packets and get them ready to send
////////////////
int check_packets(vector<msg_time> &msg_vec, int sockfd, struct sockaddr_in host_address, int packets_out)
{
	int count = 0;
	char msg[10];

	for(count; count < msg_vec.size(); count++)
	{
		// if the packet has been attempted 9 times, stop trying
		if(msg_vec[count].resubs >= 9)
		{
			packets_out--;
			ack_count++;
			msg_vec[count].set = true;
			
		}
		// If 1 second has passed
		else if((difftime(time(NULL), msg_vec[count].stime) > 1) && (msg_vec[count].resubs < 9) )
		{
			//cout << "Resubmit" << endl;
			sendto(sockfd, msg, strlen(msg), 0, (struct sockaddr *)&host_address, sizeof host_address);
			msg_vec[count].stime = time(NULL); // reset the timer.
			msg_vec[count].resubs++; // increment the descriminator
			resubmissions++; // increment for total number of resubs.
		}
		
	}

	return packets_out;

}
//////////
// end check_packets
//////////

/////////////
// clear ACKed packets
/////////////
int clear_ack_packets(vector<msg_time> &msg_vec, char* my_buf, int ack_count)
{
	vector<msg_time>::iterator iter;
	iter = msg_vec.begin();
	msg_time mytime;

	for( iter = msg_vec.begin(); iter < msg_vec.end(); iter++ )
	{
		mytime = *iter;
		if(mytime.msg_number == atoi(my_buf))
		{
			msg_vec.erase(iter); // remove this from the holder
			ack_count++;		
		}
		// If the flag was tripped in check_packets
		if(mytime.set)
			msg_vec.erase(iter);
	}
	
		

	return ack_count;
		
}
////////////
// end clear_ack_packets
////////////

//////////////////////////////////////////////////////////////////////
int main(int argc, char *argv[])
{
	int input = 0;
	int sockfd, my_rand, percent_through, SWS, num_messages, count, test_rand, packets_out;
	struct sockaddr_in host_address;
	struct hostent *host;
	int nbytes;
	
	srand(time(NULL));
	char msg[1024];
	char buf[1024];
	
	struct timeval start, stop; 
	socklen_t addr_len; 
	vector<msg_time> timers;
	msg_time message;

///////////////
////GETOPT
//////////////

	   if (argc < 5) { 
         printf("Usage: %s -h 'hostname' -w 'window size' -f '% of packets to drop' -n 'number of messages to send' -p 'port number'\n", argv[0]);
         exit(1);
		}
   

	   while ((input = getopt (argc, argv, "h:p:w:f:n:")) != -1){
         switch (input)
           {
           case 'h':
			 if ((host=gethostbyname(optarg)) == NULL) { 
				perror("gethostbyname");
				exit(1);
			 }
             break;
		   case 'p':
			   portnum = atoi(optarg);
			   break;
           case 'w':
			 SWS = atoi(optarg);
			 if(SWS >= 10 || SWS < 1){
				cout << "Send Window Size must be between 1 and 10" << endl;
				exit(1);
			 }
             break;
           case 'f':
			 percent_through = atoi(optarg);
			 if(percent_through < 0 || percent_through > 100){
					cout << "Percent_through must be between 0 and 100" << endl;
					exit(1);
				}
             break;
		   case 'n':
			 num_messages = atoi(optarg);
             if(num_messages <= 0 || num_messages >= 10000000)
			 {
				fprintf(stderr, "Invalid number of messages chosen, too few or more than 10 million.\n");
				exit(1);
			 }
             break;
           case '?':
             if ((optopt == 'h') || (optopt == 'w') || (optopt == 'p')|| (optopt == 'n'))
               cerr <<"Option -" << optopt << "%c requires an argument." << endl;
             else if (isprint (optopt))
               cerr <<"Unknown option `-" << optopt << "'."  << endl;
             else
               cerr << "Unknown option character `\\x" << optopt << "'."  << endl;
             return 1;
           default:
             abort ();
		 }
	   }

////////
//END GETOPT
////////


////////
//SOCKET SETUP
////////
	// Socket & check
	if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
		perror("socket");
		exit(1);
	}
	fcntl(sockfd, F_SETFL, O_NONBLOCK);

	host_address.sin_family = AF_INET;
	host_address.sin_port = htons(portnum);
	host_address.sin_addr = *((struct in_addr *)host->h_addr);
	addr_len = sizeof host_address;
	memset(host_address.sin_zero, '\0', sizeof host_address.sin_zero);
//////////


	packets_out = 0;
	int test_counter = 0;
	count = 0;
	int sent_msgs = 0;
	while(ack_count < num_messages)
	{

		// If the window is not full, send another message
		if(packets_out < SWS)
		{

			message.stime = time(NULL);
			message.msg_number = sent_msgs;
			message.set = false;
			timers.push_back(message);
			
			// Packet Dropping
			test_rand = (rand() % 100) + 1;
			packets_out++;
			sprintf(msg,"%d", sent_msgs);
			sent_msgs++;

			if(test_rand >= percent_through) 
			{	
				sendto(sockfd, msg, strlen(msg), 0,(struct sockaddr *)&host_address, sizeof host_address);
				//cout << "Sent:  " << msg << endl;
				
			}

		}
		// If the window is full
		else
		{
			
			// Note that this recvfrom is non-blocking
			nbytes = recvfrom(sockfd, buf, 1023 , 0,(struct sockaddr *)&host_address,&addr_len);
			if(nbytes != -1)
			{	
				// Need another check to make sure proper ack
				ack_count = clear_ack_packets(timers, buf, ack_count);
				//cout << "Ack: " << ack_count << endl;
				packets_out--;
			}

			packets_out = check_packets(timers, sockfd, host_address, packets_out);


		}

	}

	char * lastmsg  = "-22";
    nbytes = sendto(sockfd,lastmsg,strlen(lastmsg), 0,(struct sockaddr *)&host_address, sizeof host_address); 
    close(sockfd);
	cout << "Total Number of Messages Submitted: " << resubmissions + num_messages << endl;
	cout << "Number of Resubmissions: " << resubmissions << endl;
	return 0;
}
